#pragma once
#include "game_entity.h"
#include "game_entity_list.h"
#include "MessageRouter.h"

//move this out - need to factor out input.h
#include "game.h"


#include "item.h"
#include "game_chunk.h"

//move this out - replace with graphics_debug class
#include "debug_graphics.h"

Uint64 game_entity_last_created_id = 0;

int IsSet(const Entity * e, Uint32 flags){
	return e->flags & flags;
}

Entity * _make_entity(EntityType et, Chunk * chunk_array[G::CHUNK_COUNT][G::CHUNK_COUNT],
	V2 rel_pos, V2 size, Uint8 spriteID, Chunk * chunk, bool controlable = false)
{
	assert(chunk);
	Entity * Result = new Entity();

	//Result->_observers = new ObserverCollection();
	entity_set_rel_pos(chunk_array, Result,rel_pos);
	Result->_type = et;
	Result->id = game_entity_last_created_id++;
	Result->size = size;
	Result->sprite_index =spriteID;
	Result->inventory = 0;
	Result->input = (controlable ? new GameInput() : 0);
	chunk->entitys->add(Result);
	

	/*	adding argument to callback
		this section should be loosely coupled
	*/
	//OBS::Register(Result->_observers,Result,game_chunk_on_entity_move,
	//	on_entity_move_trgs, on_entity_move_trg_args);
	
	return Result;
}


LOCAL_CONSTANT V2 player_size = {28,30};
LOCAL_CONSTANT V2 default_entity_size = {28,28};
LOCAL_CONSTANT V2 default_item_entity_size = {15,15};


//toooo much repeated code here! eg. _make_entity
Entity * game_create_entity(Chunk * chunk_array[G::CHUNK_COUNT][G::CHUNK_COUNT],
	Chunk * chunk, EntityType et, V2 _pos, int item_amount, int item_type)
{
	Entity * e = 0;
	
	switch (et)
	{
	case ENTITY_TYPE_UNKNOWN: {e =	_make_entity(et,chunk_array, _pos,default_entity_size,3,chunk); } break;
	case ENTITY_TYPE_PLAYER: 
		{
			e = _make_entity(et,chunk_array, _pos,player_size,2, chunk, true);
			e->flags |= ENTITY_PLAYER | ENTITY_TILE_COLLISION | ENTITY_ENTITY_COLLISION
				| ENTITY_HAS_GRAVITY;
			e->inventory = new Inventory();
			//game->player_count++;
		} break;

	case ENTITY_TYPE_WOODCHIPPER: {
		e = _make_entity(et,chunk_array, _pos,default_entity_size,6,chunk);
		e->flags |= ENTITY_RIGHT_CLICKABLE | ENTITY_TAKES_ITEM;
		e->inventory = new Inventory();
		e->other = new Inventory();
		inventory_add_item(e->other,ITEM_WOOD_LOG,1);
		} break;

	case ENTITY_TYPE_ITEM:
		{
			e = _make_entity(et,chunk_array,_pos,default_item_entity_size,0,chunk);//_make_entity_dynamic(game,chunk,_pos,v2(0,0),default_item_entity_size,0,ENTITY_COLLISION | ENTITY_IS_ITEM | ENTITY_LOW_PRIORITY);
			e->flags |= ENTITY_TILE_COLLISION | ENTITY_ENTITY_COLLISION |ENTITY_IS_ITEM |ENTITY_HAS_GRAVITY ;
			e->item_amount = item_amount;
			e->item_type = item_type;
		}break;
	case ENTITY_TYPE_PROJECTILE:
		{
			V2i size = {8,8};
			e = _make_entity(et,chunk_array,_pos,size,1,chunk);
			e->flags |= ENTITY_ENTITY_COLLISION | ENTITY_HAS_GRAVITY;
			//add destructable to here
		}break;

	default:
		{
			e = _make_entity(et,chunk_array,_pos,v2i(32,32),0,chunk);
		}break;
	}



	assert(e);
	return e;
}

void entity_destroy(Entity * e)
{
	int pid[4] = {};
	int abs_pos[4] = {};
	void* ptr[4] = {game_get()->_chunks,0,0,0};
	MessageRouter_SpawnTriggeredMessage(game_get()->_message_router,e,"on_destroy");
#if 0
	MessageData_Create(pid,abs_pos,ptr,"entity_destroyed"));

	e->flags = ENTITY_DESTROYED;
	e->id = -1;
	e->input = 0;
	e->inventory = 0;
	e->item_amount = -1;
	e->item_type = -1;
	e->other = 0;
	e->size = v2(0,0);
	e->sprite_index = -1;
	e->_acc_ = v2(0,0);
	e->_pos_.chunk_index = v2i(-1,-1);
	e->_pos_.relative.x = -1;
	e->_pos_.relative.y = -1;
	e->_pos_.world.x = -1;
	e->_pos_.world.y = -1;
	e->_type = EntityType::ENTITY_TYPE_UNKNOWN;
	#endif 
}


//make this work with nearest chunks
Entity *  game_get_closest_entity_with_flags(Chunk * chunk, V2 world_point, Entity * exception, Uint8 flags, float max_range_sqrd)
{
	float dist_sqrd = max_range_sqrd;
	Entity * Result = 0;
	for (unsigned int i = 0; i < chunk->entitys->size(); i++){
		Entity * game_entity = chunk->entitys->at(i);
		if(exception == NULL || !(exception == game_entity)){
			if (IsSet(game_entity,flags)){
				Position * entity_pos = entity_get_position(game_entity);
				float test_dist = world_point.distance_to_squared(entity_pos->world);
				if (test_dist < dist_sqrd)
				{
					dist_sqrd = test_dist;
					Result = game_entity;
				}
			}
		}
	}
	return Result;
}


void default_entity_collision_callback(float dt, Entity * current, Entity * other )
{
	
	float push = 0.08f;
	float spread = 12.0f;
	assert(current);assert(other);
	if (current && other)
	{
		if (IsSet(current,ENTITY_IS_ITEM))
		{
			Position * current_pos = entity_get_position(current);
			Position * other_pos = entity_get_position(other);

			V2 vel = (current_pos->world + current->size*0.6f) - (other_pos->world + other->size*0.5f);
			entity_add_vel(dt,current, vel*spread);

			if (IsSet(other,ENTITY_IS_ITEM)){
				entity_add_vel(dt, other, (vel*(-1.0f*spread)) + current->_velocity_*push);
			}

			entity_set_vel( current, current->_velocity_*(1.0f-push));
		}
	}

	if (current->input && current->input->down[KEY_SPACE] && IsSet(other, ENTITY_IS_ITEM))
	{
		LOG_ERROR("picking up item");
		if (current->inventory)
		{
			inventory_add_item(current->inventory, (ItemType)current->item_type ,1);
			entity_destroy(other);
		}
	}

}

/*
MessageData player_tile_interact(void * sender, std::string message, MessageData md)
{

}
*/

GLOBAL_CONSTANT V2 ENTITY_ACC_FRICTION = {-0.9f,-0.9f};
GLOBAL_CONSTANT V2 ENTITY_VEL_FRICTION = {-0.4f,-0.1f};
GLOBAL_CONSTANT float ENTITY_MOVE_SPEED = 240.0;

//how much the entity floats
GLOBAL_CONSTANT float ENTITY_JUMP_ACC = 160.0f;
GLOBAL_CONSTANT float ENTITY_JUMP_RANGE = 2.11f;
GLOBAL_CONSTANT float ENTITY_GRAVITY = 7.08f;
LOCAL_CONSTANT float BELT_SPEED =6.0f;
GLOBAL_CONSTANT V2 ENTITY_REBOUND = {0.07f,0.01f};
GLOBAL_CONSTANT float ENTITY_JUMP_VEL = 160.0f;
GLOBAL_CONSTANT float ENTITY_MAX_REACH = 16000.0f;


//"chunk" variable is getting corrupted...
bool entity_update(float dt, Chunk * chunk_array[G::CHUNK_COUNT][G::CHUNK_COUNT], 
	V2 camera_pos ,Chunk * chunk, Entity * current_entity)
{
	assert(dt != 0);
	assert(current_entity);
	assert(chunk_array);
	assert(chunk);

	if (IsSet(current_entity, ENTITY_DESTROYED))
	{
		/*TO DO:
			fix this
			delete current_entity;
		*/

		return false;

	}

	GameInput input = GameInput();

	if (current_entity->input)
	{
		input = *current_entity->input;
	}

	Position initial_pos = *entity_get_position(current_entity);
	V2 size = current_entity->size;
	if (IsSet(current_entity, ENTITY_ENTITY_COLLISION))
	{
		SDL_Rect this_col_bounds = {initial_pos.world.x,initial_pos.world.y,size.x,size.y};

		for(std::vector<Entity*>::iterator other_entity = chunk->entitys->_vec->begin(); 
		other_entity!= chunk->entitys->_vec->end(); 
		++other_entity)
		{
			if (*other_entity != current_entity)
			{
				Position other_pos = *entity_get_position(*other_entity);
				SDL_Rect test_col_bounds = 	{other_pos.world.x,other_pos.world.y,
											 (*other_entity)->size.x,	(*other_entity)->size.y};

				SDL_Rect intersection = {};

				SDL_bool collision = SDL_IntersectRect(&this_col_bounds,&test_col_bounds,&intersection);
				if (collision == SDL_TRUE){
					//graphics_add_rect_to_stack(initial_pos.world,size,200,0,0,8);
					default_entity_collision_callback(dt, current_entity,*other_entity);
				}
			}
		}
	}
	

	//basic imput handling
	int dx = 0;	int dy = 0;
	if (input.down[KEY_LEFT]){dx--;}
	if (input.down[KEY_RIGHT]){dx++;}
	V2 desired_addition_velocity = v2(dx,dy)*ENTITY_MOVE_SPEED * dt;

	Position mouse_pos = {};
	float distance_to_mouse = 0;
	Chunk * clickedChunk = 0;
	
	bool mouse_key_down = input.down[KEY_MOUSE_RIGHT] || input.down[KEY_MOUSE_LEFT];
	if (mouse_key_down)
	{
		position_set(game_input_get_mouse_pos(),mouse_pos,camera_pos);
		distance_to_mouse = mouse_pos.world.distance_to_squared(initial_pos.world + v2(size.x/2.0f,size.y/2.0f ) );
		clickedChunk = game_get_chunk(chunk_array, mouse_pos.chunk_index);
	}

	//spit out item
	if (input.pressed[KEY_MOUSE_RIGHT])
	{
		Entity * item_entity = 0;
		ItemType i_type = (ItemType)0;
		if (inventory_remove_any_item(current_entity->inventory, i_type))
		{
			item_entity = game_create_entity(chunk_array,chunk, ENTITY_TYPE_ITEM ,initial_pos.world,1,i_type);
			V2 push_velocity = (mouse_pos.world - (initial_pos.world + size*0.5f))*5.0f;
			entity_set_vel(item_entity,push_velocity );
		}
	}

	//temp projectile code
	if(current_entity->_type == ENTITY_TYPE_PROJECTILE)
	{
		Chunk * target_chunk = game_get_chunk_at_pos(chunk_array,initial_pos.world);
		V2i tile_index = initial_pos.relative / v2(G::GRAPHICS_TILE_SIZE);
		if (!IsSet(target_chunk,tile_index,TILE_DESTROYED) &&
			IsSet(target_chunk,tile_index,TILE_DESTRUCTABLE))
		{
			Destructable * des = target_chunk->destructable[tile_index.y][tile_index.x];
			des->cur_health--;
			if (des->cur_health <0)
			{
				target_chunk->flags[tile_index.y][tile_index.x] &= ~(TILE_COLLISION | TILE_DESTRUCTABLE ) ;
				target_chunk->flags[tile_index.y][tile_index.x] |= TILE_DESTROYED;
				for(int i = 0; i < 5; i++)
				{
					game_create_entity(chunk_array,chunk, ENTITY_TYPE_ITEM,initial_pos.world,0,0);
				}
			}
			debug_graphics_add_rect_to_stack(target_chunk->id * G::CHUNK_SIZE_PIXELS + initial_pos.relative, 
				size,128,0,128,200);
		}
	}


	//destroy tile and create items
	if (input.down[KEY_MOUSE_LEFT] && distance_to_mouse < ENTITY_MAX_REACH)
	{
		V2i tile_index = mouse_pos.relative / v2(G::GRAPHICS_TILE_SIZE);

		bool valid_tile = true;
		if(IsSet(clickedChunk,tile_index,TILE_DESTROYED)){valid_tile = false;}
		if(!IsSet(clickedChunk, tile_index,TILE_DESTRUCTABLE)){valid_tile = false;}

		//graphics_add_rect_to_stack(clickedChunk->id*G::CHUNK_SIZE_PIXELS,v2i(G::CHUNK_SIZE_PIXELS),200,0,0,64);
		//graphics_add_rect_to_stack(clickedChunk->id*G::CHUNK_SIZE_PIXELS + tile_index * G::GRAPHICS_TILE_SIZE ,v2i(G::GRAPHICS_TILE_SIZE),200,0,0,64);

		bool destroyed_tile = false;
		if (valid_tile){
			debug_graphics_add_rect_to_stack(mouse_pos.world,size,0,128,0,200);
			Destructable * des = chunk->destructable[tile_index.y][tile_index.x];
			int damange_amount = 2;
			int oldHP = des->cur_health;
			int newHP = des->cur_health - damange_amount;

			//lets see if we should add a new item to the attacker
			
			/*
			for (int itemIndex= 0; itemIndex < des->itemAmount; itemIndex++){
				float lootCount = (float)itemIndex * ((float)des->max_health / (float)des->itemAmount);
				if ( math_between(lootCount,oldHP,newHP)){
					inventory_add_item(current_entity->inventory,(ItemType)des->itemID,1);
					break;
				}
			}
			*/

			if (newHP > 0){
				des->cur_health -= damange_amount;
			}
			else{
				destroyed_tile = true;
			}
		}

		if (destroyed_tile)
		{
			clickedChunk->flags[tile_index.y][tile_index.x] &= ~(TILE_COLLISION | TILE_DESTRUCTABLE ) ;
			clickedChunk->flags[tile_index.y][tile_index.x] |= TILE_DESTROYED;

			V2 item_pos = mouse_pos.world
				- v2((int)mouse_pos.world.x%G::GRAPHICS_TILE_SIZE,(int)mouse_pos.world.y%G::GRAPHICS_TILE_SIZE)
				+ v2(default_item_entity_size.x,default_item_entity_size.y)*0.5;
			Entity * item_entity = game_create_entity(chunk_array,chunk, ENTITY_TYPE_ITEM ,item_pos,1,1);
			V2 push_velocity = v2(0,-3);
			entity_set_vel(item_entity,push_velocity );
		}
	}

	V2 jump_test_pos = initial_pos.relative + v2(0,ENTITY_JUMP_RANGE + current_entity->_velocity_.y*dt);
	//V2 jump_pos = {};
	
	//check for collisions
	bool yCol = false;
	bool xCol = false;
	bool onGround = false;
	
	//can only be on ground if collides with tiles
	//add this as a flag?
	if(IsSet(current_entity,ENTITY_TILE_COLLISION))
	{
		Position jump_test_pos_full = position_set(chunk->id,jump_test_pos,jump_test_pos_full);
		onGround = game_tile_rect_collision_new(chunk_array, chunk,jump_test_pos_full,size);

		bool jumped = false;
		if (onGround && input.pressed[KEY_UP])
		{
			entity_set_acc( current_entity, v2(0,-ENTITY_JUMP_ACC));
			desired_addition_velocity.y -= ENTITY_JUMP_VEL;
			jumped = true;
		}

		if (onGround)
		{
			//printf("On Ground");
		}

		//use game_tile_rect_collision_new(target flag) instead
		if (IsSet(chunk,jump_test_pos_full.relative/v2(G::GRAPHICS_TILE_SIZE),TILE_BELT))
		{
			printf("On Belt");
			desired_addition_velocity.x += BELT_SPEED;
		}
	}

	if (IsSet(current_entity, ENTITY_HAS_GRAVITY))
	{
		desired_addition_velocity.y += ENTITY_GRAVITY;
	}

	if(IsSet(current_entity,ENTITY_TILE_COLLISION))
	{
		//checking for collisions
		V2 res_col_y = {};
		V2 res_col_x = {};
		V2 next_pos = initial_pos.relative +
			(current_entity->_velocity_ + 
			desired_addition_velocity) * dt;

		Position y_col_pos = position_set(chunk->id,v2(initial_pos.relative.x, next_pos.y),y_col_pos);
		Position x_col_pos = position_set(chunk->id,v2(next_pos.x, initial_pos.relative.y),x_col_pos);

		yCol = game_tile_rect_collision_new(chunk_array, chunk,y_col_pos,size);
		xCol = game_tile_rect_collision_new(chunk_array, chunk,x_col_pos,size);
	}

	//friction
	entity_add_vel(dt,current_entity,current_entity->_velocity_*ENTITY_VEL_FRICTION);	
	entity_add_acc(dt, current_entity,current_entity->_acc_*ENTITY_ACC_FRICTION);
	
	if(IsSet(current_entity,ENTITY_TILE_COLLISION))
	{
		if (!yCol){
			entity_add_vel(dt, current_entity, v2(0,desired_addition_velocity.y)*(1.0f/ (dt)));
			entity_add_pos(chunk_array,dt, current_entity, v2(0,current_entity->_velocity_.y));
		}else{
			entity_set_vel(current_entity, v2(current_entity->_velocity_.x,-ENTITY_REBOUND.y*current_entity->_velocity_.y));
			if (math_get_signf(desired_addition_velocity.y) != math_get_signf(current_entity->_velocity_.y))
			{
				//if we are moveing the opposite direction to collision
				entity_add_vel(dt, current_entity, v2(0,desired_addition_velocity.y)*(1.0f/ dt));
			}
		}

		if (!xCol){
			entity_add_vel(dt, current_entity, v2(desired_addition_velocity.x,0)*(1.0f/ dt));
			entity_add_pos(chunk_array, dt, current_entity, v2(current_entity->_velocity_.x,0));
		}
		else
		{
			entity_set_vel(current_entity, v2(-ENTITY_REBOUND.x*current_entity->_velocity_.x,current_entity->_velocity_.y));
			if (math_get_signf(desired_addition_velocity.x) != math_get_signf(current_entity->_velocity_.x))
			{
				//if we are moveing the opposite direction to collision
				entity_add_vel(dt, current_entity, v2(desired_addition_velocity.x,0)*(1.0f/ dt));
			}
		}
	}
	else	//ENTITY_TILE_COLLISION == false
	{
		entity_add_vel(dt, current_entity, desired_addition_velocity*(1.0f/ dt));
		entity_add_pos(chunk_array, dt, current_entity, current_entity->_velocity_);
	}
	//entity_add_pos(game, current_entity, current_entity->_velocity_);
	entity_add_vel(dt, current_entity, current_entity->_acc_);
	return true;
}


LOCAL_CONSTANT float VELOCITY_LIMIT = (250.0f);

void entity_set_vel(Entity * e, V2 vel)
{
	e->_velocity_ =vel;	
	math_cap_values(e->_velocity_,v2(-VELOCITY_LIMIT),v2(VELOCITY_LIMIT));
}

void entity_add_vel(float dt, Entity * e, V2 vel)
{
	entity_set_vel( e,e->_velocity_ + vel*dt);
}

void entity_set_rel_pos(Chunk * chunk_array[G::CHUNK_COUNT][G::CHUNK_COUNT], Entity * e, V2 rel_pos)
{
	Position old_pos = e->_pos_;
	position_set(e->_pos_.chunk_index,rel_pos,e->_pos_);

	int pid[4] = {old_pos.chunk_index.x, old_pos.chunk_index.y,
		e->_pos_.chunk_index.x, e->_pos_.chunk_index.y,};

	int abs_pos[4] = {old_pos.world.x, old_pos.world.y,
		e->_pos_.world.x, e->_pos_.world.y,};

	void* ptr[4] = {chunk_array,0,0,0};

#if 0
	MessageRouter_SpawnTriggeredMessage(game_get()->_message_router,e,"on_move",
		MessageData_Create(pid,abs_pos,ptr,"entity_moved"));
#endif 
}

void entity_add_pos(Chunk * chunk_array[G::CHUNK_COUNT][G::CHUNK_COUNT], float dt, Entity * e, V2 pos)
{
	V2 next_pos = e->_pos_.relative + (pos*dt);
	
	entity_set_rel_pos(chunk_array, e, next_pos);
}

void entity_set_acc(Entity * e, V2 acc)
{
	e->_acc_ = acc;
}

void entity_add_acc(float dt, Entity * e, V2 acc)
{
	entity_set_acc(e, e->_acc_ + acc*dt);
}

Position * entity_get_position(Entity * e)
{
	assert(&e->_pos_);
	return &e->_pos_;
}
